'use strict';

var log4js = require('log4js');
var commandLineArgs = require('command-line-args')
var named = require('named');
var spawn = require('child_process').spawn;
var dns = require('dns');

var optionDefinitions = [
  { name: 'btc_default', alias: 'b', type: String },
  { name: 'ltc_default', alias: 'l', type: String },
];

var logger = log4js.getLogger('default');
logger.level = 'debug';
var options = commandLineArgs(optionDefinitions);

if(!options.btc_default) {
	logger.error('no btc_default specified');
	return;
}

if(!options.ltc_default) {
	logger.error('no ltc_default specified');
	return;
}

var ttl = 3600;
var port = 53;
var cache = {};

function listenOnThisIP(ip, coin) {
	var key = '[' + coin + ' server@' + ip + ':' + port + ']';

	var server = named.createServer();

	server.listen(port, ip, function() {
		logger.info('%s started', key);
	});

	server.on('error', function(err) {
		logger.error('%s error', key, err);
	});

	server.on('query', function(query) {
		var domain = (query.name()||'').toLowerCase();
		var client = query._client.address || 'unknown';

		logger.info('%s receive query %s from %s', key, domain, client);

		if(justForward(domain)) {
			solveRaw(domain, function(ok, addr) {
				if(!ok) {
					logger.error('%s forward query %s from %s failed', key, domain, client);
					return;
				}

				response(query, domain, addr);
				logger.info('%s forward query %s from %s ok, result is %s', key, domain, client, addr);
			});
			return;
		}

		var dft = options[coin + '_default'];
		if(/^\d+\.\d+\.\d+\.\d+$/.test(dft)) {
			// default is IP
			response(query, domain, dft);
			logger.info('%s response %s from %s ok, result is default ip %s', key, domain, client, dft);
			return;
		}

		// default is domain
		solveRaw(dft, function(ok, addr) {
			if(!ok) {
				logger.error('%s solve default %s failed', key, dft);
				return;
			}

			response(query, domain, addr);
			logger.info('%s response %s from %s ok, result is default ip %s from %s', key, domain, client, addr, dft);
		});
	});

	server._ip = ip;

	function response(query, domain, addr) {
		var target = new named.ARecord(addr);
		query.addAnswer(domain, target, ttl);
		server.send(query);
	}

	return server;
}

function justForward(domain) {
	if(domain == 'vps2.iminer.vip') return true;
	if(domain == 'vps1.iminer.vip') return true;
	if(domain == 'easy2mine.com') return true;
	if(domain == 'b.top') return true;
	if(domain == 'btc.top' || domain == 'ltc.top') return true;
	if(/\.btc\.top$/i.test(domain)) return true;
	if(/\.ltc\.top$/i.test(domain)) return true;
	if(/\.pandabtc\.cn$/i.test(domain)) return true;
	if(/\.easy2mine\.com$/i.test(domain)) return true;
	if(/\.b\.top$/i.test(domain)) return true;
	if(/\.ntp\.org$/i.test(domain)) return true;
	return false;
}

function solveRaw(domain, cb) {
	var old = cache[domain];
	if(old && new Date() - old.at < 300 * 1000) {
		// 5 分钟
		cb(true, old.addr);
		return;
	}
	dns.lookup(domain, function(err, address, family) {
		if(err) {
			logger.error('solveRaw failed: %s', domain, err);
			cb(false);
			return;
		}

		if(family != 4) {
			logger.error('solveRaw failed to get IPv4: %s', domain, family);
			cb(false);
			return;
		}

		cache[domain] = {
			at: new Date(),
			addr: address,
		};
		cb(true, address);
	});
}

var interfaces = {};
var interfaceServers = {};

function parseIfconfigResponse(content) {
	var lines = content.split(/\n/);
	var ifname = null;
	for(var line of lines) {
		var m = line.match(/^(\S+)/);
		if(m) {
			ifname = m[1].replace(/:$/, '');
			continue;
		}

		if(!ifname) {
			continue;
		}

		m = line.match(/inet (\d+\.\d+\.\d+\.\d+)\s/);
		if(!m) {
			m = line.match(/inet addr:(\d+\.\d+\.\d+\.\d+)\s/);
		}
		if(!m) {
			continue;
		}

		var ifaddr = m[1];
		if(!ifaddr) {
			continue;
		}

		interfaces[ifname] = ifaddr;

		var server = interfaceServers[ifname];
		if(server) {
			if(server._ip != ifaddr) {
				// ip changed
				logger.info('server on %s closed because ip changed from %s to %s', ifname, server._ip, ifaddr);
				server.close();
				server = interfaceServers[ifname] = null;
			}
		}

		if(!server) {
			if(/:1$/.test(ifname)) {
				server = listenOnThisIP(ifaddr, 'btc');
			} else if(/:2$/.test(ifname)) {
				server = listenOnThisIP(ifaddr, 'ltc');
			}
			interfaceServers[ifname] = server;
		}
		ifname = null;
	}
}

function runIfconfig() {
	var c = spawn('/sbin/ifconfig', []);
	var data = '';
	c.stdout.on('data', (d) => {
		data += d;
	});
	c.on('close', (code) => {
		if(code) {
			logger.error('ifconfig failed', code);
			return;
		}

		parseIfconfigResponse(data);

		logger.debug('current all interfaces: %s', JSON.stringify(interfaces));
	});
}

setInterval(runIfconfig, 5000);
runIfconfig();

